FreeRTOS 临界区
在并行编程中,并行存取共享的资源时,常常会导致意外和错误的结果。例如下面代码task1和task2都通过串口打印消息,但由于任务调度,消息被截断了。
1#include <stm32f4xx.h>
2#include <FreeRTOS.h>
3#include <task.h>
4#include <uart.h>
5
6void task1(void* args);
7void task2(void* args);
8
9int main()
10{
11 //配置USART1
12 USART1_Config();
13 //创建任务
14 TaskHandle_t h1,h2;
15 xTaskCreate(task1,"task1",configMINIMAL_STACK_SIZE,NULL,1,&h1);
16 xTaskCreate(task2,"task2",configMINIMAL_STACK_SIZE,NULL,1,&h2);
17 //开启任务调度
18 vTaskStartScheduler();
19 while(1);
20}
21
22
23void task1(void* args)
24{
25 USART_printf(USART1,"task1 is running.\n");
26 vTaskDelete(NULL);
27}
28
29void task2(void* args)
30{
31 USART_printf(USART1,"task2 is running.\n");
32 vTaskDelete(NULL);
33}类似的,当修改公共变量时任务调度可能导致变量的值错误、操作时序严格的设备时任务调度可能导致设备不能正常工作。
因此,访问共享资源的部分代码会被保护起来,在执行这段代码时不进行任务调度,这样的代码段称为临界区(Critical Section)。
FreeRTOS通过两个包含在task.h里的宏taskENTER_CRITICAL()、taskEXIT_CRITICAL()来进入和离开临界区在。taskENTER_CRITICAL()之后、taskEXIT_CRITICAL()之前不会切换到其他任务。
修改USART_printf函数,在打印消息前后加入taskENTER_CRITICAL()和taskEXIT_CRITICAL(),这样在USART_printf运行期间就不会切换任务,打印的消息也就不会被截断。
1#define USART_PRINTF_BUFFER_SIZE 256
2
3int USART_printf(USART_TypeDef* port,const char* fmt,...)
4{
5 char str[USART_PRINTF_BUFFER_SIZE];
6 int length,index;
7 va_list argList;
8 va_start(argList,fmt);
9 length = vsnprintf(str,USART_PRINTF_BUFFER_SIZE-1,fmt,argList);
10 va_end(argList);
11 taskENTER_CRITICAL();//进入临界区
12 for(index = 0 ; index < length ; index++)
13 {
14 USART_WriteByte(port,str[index]);
15 }
16 taskEXIT_CRITICAL();//离开临界区
17 return length;
18}临界区嵌套是安全的,因为内核有维护一个嵌套深度计数。临界区只会在嵌套深度为0时才会真正退出——即在为每个之前调用的taskENTER_CRITICAL()都配套调用了taskEXIT_CRITICAL()之后。
临界区是互斥功能的一种非常原始的实现方式,通常只是关闭所有中断从而使任务调度暂停。这样可能会导致任务不能正常执行,中断不能及时响应等问题。所以临界区应当只具有很短的时间。上例中打印串口消息是一个相当耗费时间的过程,并不适合使用临界区。
另外也可以将调度器挂起来保护临界区,vTaskSuspendAll挂起调度器、xTaskResumeAll恢复调度器,在vTaskSuspendAll和xTaskResumeAll之间不会进行任务调度,但可以响应中断。
1#include <task.h>
2void vTaskSuspendAll(void);
3BaseType_t xTaskResumeAll(void);